#include "stdafx.h"

#include<stack>

#include "Database.h"
#include "TextDisplay.h"
#include "InputManager.h"
const int CharDisplay::FontXCount = 10;
const int DamageDisplay::UpingSpeed = 1;
const int DamageDisplay::ShowCount = 30;

CharDisplay::CharDisplay(int yCount = defaultFontYCount){
	this->CharDisplay::CharDisplay(defaultFontFace, yCount);
}
CharDisplay::CharDisplay(string fileName, int yCount = defaultFontYCount){
	this->CharDisplay::CharDisplay(Bitmap::getBitmap(fileName), yCount);
}
CharDisplay::CharDisplay(Bitmap* bitmap, int yCount = defaultFontYCount) : Sprite(bitmap) {
	fontYCount = yCount; setup(-1,1);
}
int CharDisplay::getFontWidth() { return getSpriteWidth(); }
int CharDisplay::getFontHeight() { return getFrameRectHeight()/2; }

int CharDisplay::getFrameRectX() {
	return (index % FontXCount)*getFrameRectWidth();
}
int CharDisplay::getFrameRectY() {
	return (index / FontXCount)*getFrameRectHeight();
}
int CharDisplay::getFrameRectWidth() {
	return width / FontXCount * length;
}
int CharDisplay::getFrameRectHeight() {
	return height / fontYCount;
}
void CharDisplay::setup(int index,int length) {
	this->index = index;
	this->length = length;
	refresh();
}
void CharDisplay::setIndex(int index) {
	if (this->index != index) {
		this->index = index; refresh();
	}
}
void CharDisplay::setLength(int length) {
	if (this->length != length) {
		this->length = length; refresh();
	}
}
void CharDisplay::refresh() { 
	if(index >= 0) refreshFrame(); 
	else clearFrame();
}
void CharDisplay::clearFrame() {
	setFrameRect(Rect(0,0,0,0));
}
void CharDisplay::refreshFrame() {
	setFrameRect(Rect(getFrameRectX(), getFrameRectY(), getFrameRectWidth(), getFrameRectHeight()));
}



TextDisplay::TextDisplay() {this->TextDisplay::TextDisplay(defaultFontFace);}
TextDisplay::TextDisplay(string fileName) {this->TextDisplay::TextDisplay(Bitmap::getBitmap(fileName));}
TextDisplay::TextDisplay(Bitmap* bitmap) { fontFace = bitmap; setLineHeightRate(1.2);}
TextDisplay::~TextDisplay() { clearCharDisplays(); }

void TextDisplay::setLineHeightRate(double rate) {
	if (lineHeightRate != rate) {
		lineHeightRate = rate; refresh();
	}
}
void TextDisplay::setText(string text) {
	if (this->text != text) {
		this->text = text; refresh();
	}
}
string TextDisplay::getText() { return text; }

int TextDisplay::getWidth() { return textWidth; }
int TextDisplay::getHeight() { return textHeight; }

void TextDisplay::refresh() {
	clearCharDisplays(); 
	processText();
}
void TextDisplay::processText() {
	textWidth = textX = textY = 0; char c;
	int lineHeight = 0;
	for (int i = 0; i < text.size(); i++) {
		processChar(lineHeight, text[i]);
	}
	textWidth = max(textWidth, textX);
	textHeight = textY + lineHeight;
}
void TextDisplay::processChar(int &lineHeight,char c) {
	if (c == '\n') {
		textY += lineHeight; lineHeight = 0;
		textWidth = max(textWidth, textX);
		textX = 0;
	}else {
		CharDisplay* disp = addCharDisplay(c);
		disp->x = textX; disp->y = textY;
		lineHeight = max(lineHeight,
			int(disp->getFontHeight()*lineHeightRate));
		textX += disp->getFontWidth();
	}
}

int TextDisplay::getCharIndex(char c) {
	if (c >= '0' && c <= '9') return c - '0';
	if (c >= 'A' && c <= 'Z') return 10 + c - 'A';
	if (c >= 'a' && c <= 'z') return 40 + c - 'a';
	switch(c) {
	case ' ': return 36; break;
	case ',': return 37; break;
	case '.': return 38; break;
	case '-': return 39; break;
	case '!': return 66; break;
	case '?': return 67; break;
	case ':': return 68; break;
	case '*': return 69; break;
	case '/': return 70; break;
	case '(': return 71; break;
	case ')': return 72; break;
	case '[': return 73; break;
	case ']': return 74; break;
	case '+': return 75; break;
	case '=': return 76; break;
	case '_': return 77; break;
	case '%': return 78; break;
	case '&': return 79; break;
	default: return 36; break;
	}
}
int TextDisplay::getCharLength(char c) { return 1; }

CharDisplay* TextDisplay::addCharDisplay(char c) {
	CharDisplay* disp = new CharDisplay(fontFace);
	disp->setup(getCharIndex(c), getCharLength(c));
	charDisplays.push_back(disp);
	addChild(disp);
	return disp;
}
void TextDisplay::clearCharDisplays() {
	for (int i = 0; i < charDisplays.size(); i++) {
		removeChild(charDisplays[i]);
		delete charDisplays[i];
	}
	charDisplays.clear();
}

NumberDisplay::NumberDisplay() { this->NumberDisplay::NumberDisplay(defaultFontFace); }
NumberDisplay::NumberDisplay(string fileName) { this->NumberDisplay::NumberDisplay(Bitmap::getBitmap(fileName)); }
NumberDisplay::NumberDisplay(Bitmap* bitmap) { fontFace = bitmap; setLineHeightRate(1.2); }

void NumberDisplay::setNum(int num){
	if (this->num != num) {
		this->num = num; refresh();
	}
}
int NumberDisplay::getNum() { return num; }

void NumberDisplay::processText() {
	textWidth = textX = textY = 0; char c;
	int lineHeight = 0;
	processValue(lineHeight, num);
	textWidth = max(textWidth, textX);
	textHeight = textY + lineHeight;
}
void NumberDisplay::processValue(int &lineHeight, int c) {
	stack<int> numBit; 
	do {
		numBit.push(c % 10); c /= 10;
	} while (c > 0);
	while (!numBit.empty()) {
		int num = numBit.top(); numBit.pop();
		CharDisplay* disp = addCharDisplay(num);
		disp->x = textX; disp->y = textY;
		lineHeight = max(lineHeight,
			int(disp->getFontHeight()*lineHeightRate));
		textX += disp->getFontWidth();
	}
}
CharDisplay* NumberDisplay::addCharDisplay(int c) {
	CharDisplay* disp = new CharDisplay(fontFace);
	disp->setup(getValueIndex(c), getCharLength(c));
	charDisplays.push_back(disp);
	addChild(disp);
	return disp;
}
int NumberDisplay::getValueIndex(int c) { return c; }



TypableNumberDisplay::TypableNumberDisplay(int nmax,int nmin): 
	NumberDisplay(), nmax(nmax), nmin(nmin) { 
	deactivate(); count = 0;
}
TypableNumberDisplay::TypableNumberDisplay(string fileName, int nmax, int nmin) : 
	NumberDisplay(fileName), nmax(nmax), nmin(nmin) { 
	deactivate(); count = 0;
}
TypableNumberDisplay::TypableNumberDisplay(Bitmap* bitmap, int nmax, int nmin) : 
	NumberDisplay(bitmap), nmax(nmax), nmin(nmin) {
	deactivate(); count = 0;
}

void TypableNumberDisplay::activate() { active = true; keyCd = 0; }
void TypableNumberDisplay::deactivate() { active = false; }

bool TypableNumberDisplay::isActive() { return active; }
bool TypableNumberDisplay::isChanged() { 
	bool c = changed; changed = false; return c; 
}

void TypableNumberDisplay::setNum(int num) {
	num = max(nmin, min(num, nmax));
	changed = this->num != num;
	NumberDisplay::setNum(num);
}

void TypableNumberDisplay::updateOthers() {
	NumberDisplay::updateOthers();
	updateTypingEffect();
	updateTyping();
}
void TypableNumberDisplay::updateTypingEffect() {
	if (!isActive() || charDisplays.empty()) return;
	if (++count == 5) 
		charDisplays[charDisplays.size() - 1]->shine(' ', 5);
	else if (count == 10) count = 0;
}
void TypableNumberDisplay::updateTyping() {
	if (--keyCd > 0 || !isActive()) return;
	int ud = InputManager::UD();
	if (ud != 0) {
		ud > 0 ? setNum(num - 10) : setNum(num + 10);
		keyCd = 5; return;
	}
	int lr = InputManager::LR();
	if (lr != 0) {
		lr > 0 ? setNum(num + 1) : setNum(num - 1);
		keyCd = 5; return;
	}
	if (InputManager::isAnyKeyPress()) {
		char c = InputManager::getInputKey();
		if ('0' <= c && c <= '9' || c == 8) {
			keyCd = 5;
			if (c == 8) setNum(num / 10);
			else setNum(num * 10 + (c - '0'));
		}
	}
}

DamageDisplay::DamageDisplay(Damage damage):
	TextDisplay(damageFontFace){
	setDamageData(damage);
}

void DamageDisplay::setDamageData(Damage damage) {
	damageData = damage; refresh();
}
void DamageDisplay::processText() {
	textWidth = textX = textY = 0; char c;
	processDamageData();
}
void DamageDisplay::processDamageData() {
	textWidth = textX = textY = 0; char c;
	int lineHeight = 0;
	for (int i = 0; i < 6; i++) {
		int value = damageData.getDamageValue(Damage::DamageType(i));
		if (value <= 0) continue;
		processValue(lineHeight, value, Damage::DamageType(i));
		textY += lineHeight; lineHeight = 0;
		textWidth = max(textWidth, textX);
		textX = 0;
	}
	if(damageData.getHitResult() != Damage::Hit)
		processFlag(lineHeight);
	textWidth = max(textWidth, textX);
	textHeight = textY+lineHeight;
}
void DamageDisplay::processValue(int &lineHeight, int value, Damage::DamageType type) {
	stack<int> numBit;
	while (value > 0) {
		numBit.push(value % 10); value /= 10;
	}
	while (!numBit.empty()) {
		int num = numBit.top(); numBit.pop();
		CharDisplay* disp = addCharDisplay();
		disp->setup(getValueIndex(num, type), getCharLength('\0'));
		disp->x = textX; disp->y = textY;
		lineHeight = max(lineHeight,
			int(disp->getFontHeight()*lineHeightRate));
		textX += disp->getFontWidth();
	}
}
void DamageDisplay::processFlag(int &lineHeight) {
	CharDisplay* disp = addCharDisplay();
	disp->setup(getFlagIndex(), getFlagLength());
	disp->x = textX; disp->y = textY;
	lineHeight = max(lineHeight,
		int(disp->getFontHeight()*lineHeightRate));
	textX += disp->getFontWidth();
}
CharDisplay* DamageDisplay::addCharDisplay() {
	CharDisplay* disp = new CharDisplay(fontFace,4);
	charDisplays.push_back(disp);
	addChild(disp);
	return disp;
}
int DamageDisplay::getValueIndex(int c, Damage::DamageType type) {
	if (damageData.getHitResult() == Damage::Crit) return 20 + c;
	if (type == Damage::HPRecover || type == Damage::MPRecover) return 10 + c;
	return c;
}
int DamageDisplay::getFlagIndex() {
	if (damageData.getHitResult() == Damage::Miss) return 30;
	if (damageData.getHitResult() == Damage::Crit) return 33;
}
int DamageDisplay::getFlagLength() {
	return damageData.getHitResult() == Damage::Miss ? 3 :
		(damageData.getHitResult() == Damage::Crit ? 5 : 1);
}
void DamageDisplay::updateOthers() {
	updateUping();
}
void DamageDisplay::updateUping() {
	y -= UpingSpeed;
	if (++count >= ShowCount) visible = false;
}